package com.itaming.lycheeframework.support.utils;

import com.itaming.lycheeframework.common.constant.FrameworkConstant;
import lombok.experimental.UtilityClass;

import java.nio.charset.Charset;

/**
 * Hex工具类
 *
 * @author A.Ming
 */
@UtilityClass
public class HexUtil {

    /**
     * 小写Hex字符
     */
    private static final char[] HEX_BYTES_LOWER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * 大写Hex字符
     */
    private static final char[] HEX_BYTES_UPPER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * 编码
     *
     * <p>
     * 默认使用 UTF-8 字符集和小写字符
     * <ul>
     *     <li>指定字符集请使用 {@link #encode(String, Charset)}</li>
     *     <li>指定大小写 {@link #encode(String, boolean)}</li>
     *     <li>指定字符集、指定大小写请使用 {@link #encode(String, Charset, boolean)}</li>
     * </ul>
     *
     * @param data 待编码字符串
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static char[] encode(String data) {
        return encode(data, FrameworkConstant.DEFAULT_CHARSET);
    }

    /**
     * 编码
     *
     * <p>
     * 默认使用小写字符，指定大小写请使用 {@link #encode(String, Charset, boolean)}
     *
     * @param data    待编码字符串
     * @param charset 字符集
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static char[] encode(String data, Charset charset) {
        return encode(data, charset, false);
    }

    /**
     * 编码
     *
     * <p>
     * 默认使用 UTF-8 字符集，指定字符集请使用 {@link #encode(String, Charset, boolean)}
     *
     * @param data        待编码字符串
     * @param toUpperCase 是否大写
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static char[] encode(String data, boolean toUpperCase) {
        return encode(data, FrameworkConstant.DEFAULT_CHARSET, toUpperCase);
    }

    /**
     * 编码
     *
     * @param data        待编码字符串
     * @param charset     字符集
     * @param toUpperCase 是否大写
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static char[] encode(String data, Charset charset, boolean toUpperCase) {
        return encode(StringUtil.getBytes(data, charset), toUpperCase);
    }

    /**
     * 编码
     *
     * <p>
     * 默认使用小写字符，指定大小写请使用 {@link #encode(byte[], boolean)}
     *
     * @param data 待编码数据
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static char[] encode(byte[] data) {
        return encode(data, false);
    }

    /**
     * 编码
     *
     * @param data        待编码数据
     * @param toUpperCase 是否大写
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static char[] encode(byte[] data, boolean toUpperCase) {
        return encode(data, toUpperCase ? HEX_BYTES_UPPER : HEX_BYTES_LOWER);
    }

    /**
     * 编码
     *
     * @param data     待编码数据
     * @param hexChars Hex字符
     * @return 编码后的字符数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    private static char[] encode(byte[] data, char[] hexChars) {
        if (data == null) {
            return null;
        }

        int len = data.length;
        char[] out = new char[len << 1];
        for (int i = 0, j = 0; i < len; i++) {
            out[j++] = hexChars[(0xF0 & data[i]) >>> 4];
            out[j++] = hexChars[0xF & data[i]];
        }

        return out;
    }

    /**
     * 编码并转为字符串
     *
     * <p>
     * 默认使用 UTF-8 字符集和小写字符
     * <ul>
     *     <li>指定字符集请使用 {@link #encodeToString(String, Charset)}</li>
     *     <li>指定大小写 {@link #encodeToString(String, boolean)}</li>
     *     <li>指定字符集、指定大小写请使用 {@link #encodeToString(String, Charset, boolean)}</li>
     * </ul>
     *
     * @param data 待编码字符串
     * @return 编码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String encodeToString(String data) {
        return encodeToString(data, FrameworkConstant.DEFAULT_CHARSET);
    }

    /**
     * 编码并转为字符串
     *
     * <p>
     * 默认使用小写字符，指定大小写请使用 {@link #encodeToString(String, Charset, boolean)}
     *
     * @param data    待编码字符串
     * @param charset 字符集
     * @return 编码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String encodeToString(String data, Charset charset) {
        return encodeToString(data, charset, false);
    }

    /**
     * 编码并转为字符串
     *
     * <p>
     * 默认使用 UTF-8 字符集，指定字符集请使用 {@link #encodeToString(String, Charset, boolean)}
     *
     * @param data        待编码字符串
     * @param toUpperCase 是否大写
     * @return 编码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String encodeToString(String data, boolean toUpperCase) {
        return encodeToString(data, FrameworkConstant.DEFAULT_CHARSET, toUpperCase);
    }

    /**
     * 编码并转为字符串
     *
     * @param data        待编码字符串
     * @param charset     字符集
     * @param toUpperCase 是否大写
     * @return 编码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String encodeToString(String data, Charset charset, boolean toUpperCase) {
        return encodeToString(StringUtil.getBytes(data, charset), toUpperCase);
    }

    /**
     * 编码并转为字符串
     *
     * <p>
     * 默认使用小写字符，指定大小写请使用 {@link #encodeToString(byte[], boolean)}
     *
     * @param data 待编码数据
     * @return 编码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String encodeToString(byte[] data) {
        return encodeToString(data, false);
    }

    /**
     * 编码并转为字符串
     *
     * @param data        待编码数据
     * @param toUpperCase 是否大写
     * @return 编码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String encodeToString(byte[] data, boolean toUpperCase) {
        return StringUtil.newString(encode(data, toUpperCase));
    }

    /**
     * 解码
     *
     * @param data 待解码字符串
     * @return 解码后的字节数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static byte[] decode(String data) {
        return decode(StringUtil.toCharArray(data));
    }

    /**
     * 解码
     *
     * @param data 待解码数据
     * @return 解码后的字节数组，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static byte[] decode(char[] data) {
        if (data == null) {
            return null;
        }

        int len = data.length;
        if ((len & 0x01) != 0) {
            throw new IllegalArgumentException("Hex binary needs to be even-length: " + len);
        }

        byte[] out = new byte[len >> 1];
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f |= toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }

    /**
     * 解码并转为字符串
     *
     * <p>
     * 默认使用 UTF-8 字符集，指定字符集请使用 {@link #decodeToString(String, Charset)}
     *
     * @param data 待解码字符串
     * @return 解码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String decodeToString(String data) {
        return decodeToString(StringUtil.toCharArray(data), FrameworkConstant.DEFAULT_CHARSET);
    }

    /**
     * 解码并转为字符串
     *
     * @param data    待解码字符串
     * @param charset 字符集
     * @return 解码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String decodeToString(String data, Charset charset) {
        return decodeToString(StringUtil.toCharArray(data), charset);
    }

    /**
     * 解码并转为字符串
     *
     * <p>
     * 默认使用 UTF-8 字符集，指定字符集请使用 {@link #decodeToString(char[], Charset)}
     *
     * @param data 待解码数据
     * @return 解码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String decodeToString(char[] data) {
        return decodeToString(data, FrameworkConstant.DEFAULT_CHARSET);
    }

    /**
     * 解码并转为字符串
     *
     * @param data    待解码数据
     * @param charset 字符集
     * @return 解码后的字符串，当 {@code data} 为 {@code null}，返回 {@code null}
     */
    public static String decodeToString(char[] data, Charset charset) {
        return StringUtil.newString(decode(data), charset);
    }

    /**
     * 16进制字符转数值
     *
     * @param c     字符
     * @param index 索引
     * @return
     */
    private static int toDigit(char c, int index) {
        int digit = Character.digit(c, 16);
        if (digit == -1) {
            throw new IllegalArgumentException("Illegal hexadecimal character '" + c + "' at index " + index);
        }
        return digit;
    }

}
